#include "decimal.h"
#include "../arch/asm.h"
#include "../base/logger.h"

OPEN_JLIB_NS


std::vector<Integer> Decimal::__pow10s = {
  1L,
  10L,
  100L,
  1000L,
  10000L,
  100000L,
  1000000L,
  10000000L,
  100000000L,
  1000000000L,
  10000000000L,
  100000000000L,
  1000000000000L,
  10000000000000L,
  100000000000000L,
  1000000000000000L,
  10000000000000000L,
  100000000000000000L,
  1000000000000000000L,
};
u64 Decimal::__counter = 0;


Integer Decimal::__pow10 (u32 exp) noexcept {
  Integer ten{&Integer::__Ten, 1};
  if (exp <= MaxCachedTenPower) {
    if (exp >= __pow10s.size()) {
      for (u32 i = (u32) __pow10s.size(); i <= exp; ++i)
        __pow10s.emplace_back(__pow10s[i - 1] * ten);
    }
    return __pow10s[exp];
  }
  return ten.pow(exp);
}

s32 Decimal::__cmp_half (const Integer& base, const Integer& half) noexcept {
  auto dmsb = half.msb() - base.msb();
  if (dmsb != 1)
    return dmsb ? -1 : 1;
  Integer::Num* num1 = base._num;
  Integer::Num* num2 = half._num;
  u32 carry = (num2->size - num1->size) << 31;
  for (s32 i = num1->size - 1; i >= 0; --i) {
    u32 a = num1->data[i];
    u32 b = carry | (num2->data[i] >> 1);
    if (a != b)
      return a > b ? 1 : -1;
    carry = num2->data[i] << 31;
  }
  return carry ? -1 : 0;
}

Integer Decimal::__round (const Integer& d, const Integer& q, const Integer& r, RoundMode mode, s32 sign_hint) {
  if (!r)
    return q;
  switch (mode) {
    case RoundMode::None:
      throw "Rounding necessary";
    case RoundMode::Up:
      return q + Integer::__one(sign_hint);
    case RoundMode::Down:
      return q;
    case RoundMode::Ceiling:
      return sign_hint > 0 ? q + Integer::positive_one() : q;
    case RoundMode::Floor:
      return sign_hint < 0 ? q - Integer::positive_one() : q;
    case RoundMode::HalfUp:
      return __cmp_half(r, d) >= 0 ? q + Integer::__one(sign_hint) : q;
    case RoundMode::HalfDown:
      return __cmp_half(r, d) > 0 ? q + Integer::__one(sign_hint) : q;
    case RoundMode::HalfEven:
      return q.odd() ? (__cmp_half(r, d) >= 0 ? q + Integer::__one(sign_hint) : q) : (__cmp_half(r, d) > 0 ? q + Integer::__one(sign_hint) : q);
    case RoundMode::HalfOdd:
      return q.odd() ? (__cmp_half(r, d) > 0 ? q + Integer::__one(sign_hint) : q) : (__cmp_half(r, d) >= 0 ? q + Integer::__one(sign_hint) : q);
    default:
      throw "Unknown Round Mode";
  }
}

u32 Decimal::__digit_count (const Integer& num) noexcept {
  if (!num.sign())
    return 1;
  auto r = u32(((u64(num.msb()) + 1UL) * 646456993UL) >> 31);
  return num.abs() < __pow10(r) ? r : r + 1;
}


Decimal::Decimal (double src, s32 precision) {
  ++__counter;
  if (src == 0.0) {
    _int = {};
    _pre = precision;
    _sig = 1;
    return;
  }
  u64 i = __as_uint64(src);
  s64 s = i >> 63;
  s64 e = (i << 1) >> 53;
  u64 m = i & 0xFFFFFFFFFFFFFUL;
  if (e == 0x7FF)
    throw "Infinite or NaN";
  m = e ? m | 0x10000000000000UL : m << 1;
  e -= 1075;
  for (; (m & 1) == 0; m >>= 1, ++e);

  //do {
  //  if (src == 0.0)
  //    break;
  //  u64 ival = __as_uint64(src);
  //  s64 s = ival >> 63;
  //  s64 e = (ival << 1) >> 53;
  //  u64 m = ival & 0xFFFFFFFFFFFFFUL;
  //  debug_assert(e != 0x7FF, "Inf or NaN");
  //  if (e == 0x7FFUL)
  //    break;
  //  m = e ? m | 0x10000000000000UL : m << 1;
  //  e -= 1075;
  //  for (; !(m & 1); m >>= 1, ++e);
  //  _num = Num::allocate(2);
  //  _num->data[0] = u32(m);
  //  _num->data[1] = u32(m >> 32);
  //  _sig = s32(s);
  //  _exp =
  //}
  //while (false);
  _int = {};
  _pre = 0;
  _sig = 1;
}

Decimal::Decimal (const c8* src) {
  ++__counter;
  if (!src) {
    _pre = 0;
    _sig = 1;
    return;
  }
  auto nsig = Integer::__encode_sign(*src);
  if (nsig)
    ++src;
  auto ilen = Integer::__check_digit(src);
  auto istr = src;
  src += ilen;
  auto dlen = 0U;
  auto dstr = src + 1;
  if (*src == '.') {
    dlen = Integer::__check_digit(++src);
    src += dlen;
  }
  for (; ilen && *istr == '0'; --ilen, ++istr);
  auto pre = 0;
  if (*src == 'e' || *src == 'E') {
    auto esig = Integer::__encode_sign(*++src);
    if (esig)
      ++src;
    auto elen = Integer::__check_digit(src);
    for (auto s = src; elen && *s == '0'; --elen, ++src);
    if (elen) {
      if (elen > 9)
        throw "Index is to large";
      pre = (s32)Integer::__encode_digit(src, elen);
      if (esig < 0)
        pre = -pre;
    }
  }
  if (pre == 0) {
    _int = {&Integer::__One, 1};
    _pre = 0;
    _sig = 1;
  }
  c8 local_buf[256];
  auto slen = ilen + dlen;
  auto sbuf = slen > 256 ? (c8*)malloc(slen) : local_buf;
  auto sstr = sbuf;
  if (ilen)
    memcpy(sbuf, istr, ilen);
  if (dlen)
    memcpy(sbuf + ilen, dstr, dlen);
  for (; slen > 1 && *sstr == '0'; --slen, ++sstr);
  _int = {sstr, slen};
  if (nsig < 0)
    _int._sig = -_int._sig;
  _pre = dlen - pre;
  _sig = slen;
  if (sbuf != local_buf)
    free(sbuf);
}

Decimal::Decimal (const c8* src, u32 len) {
  ++__counter;
  if (!src || !len) {
    _pre = 0;
    _sig = 1;
    return;
  }
  auto nsig = Integer::__encode_sign(*src);
  if (nsig)
    ++src, --len;
  auto ilen = Integer::__check_digit(src, len);
  auto istr = src;
  src += ilen;
  auto dlen = 0U;
  auto dstr = src + 1;
  if (len && *src == '.') {
    dlen = Integer::__check_digit(++src, --len);
    src += dlen;
    len -= dlen;
  }
  for (; ilen && *istr == '0'; --ilen, ++istr);
  auto pre = 0;
  if (len && (*src == 'e' || *src == 'E')) {
    if (--len) {
      auto esig = Integer::__encode_sign(*++src);
      if (esig)
        ++src, --len;
      auto elen = Integer::__check_digit(src, len);
      for (auto s = src; elen && *s == '0'; --elen, ++src);
      if (elen) {
        if (elen > 9)
          throw "Index is to large";
        pre = (s32)Integer::__encode_digit(src, elen);
        if (esig < 0)
          pre = -pre;
      }
    }
  }
  if (pre == 0) {
    _int = {&Integer::__One, 1};
    _pre = 0;
    _sig = 1;
  }
  c8 local_buf[256];
  auto slen = ilen + dlen;
  auto sbuf = slen > 256 ? (c8*)malloc(slen) : local_buf;
  auto sstr = sbuf;
  if (ilen)
    memcpy(sbuf, istr, ilen);
  if (dlen)
    memcpy(sbuf + ilen, dstr, dlen);
  for (; slen > 1 && *sstr == '0'; --slen, ++sstr);
  _int = {sstr, slen};
  if (nsig < 0)
    _int._sig = -_int._sig;
  _pre = dlen - pre;
  _sig = slen;
  if (sbuf != local_buf)
    free(sbuf);
}


std::string Decimal::str (bool sign) const noexcept {
  std::string res;
  if (_int.sign() < 0)
    res += '-';
  else if (sign)
    res += '+';
  if (_int.sign() == 0) {
    res += '0';
    if (_pre > 0) {
      res += '.';
      res.append(uwl(_pre), '0');
    }
    return res;
  }
  s64 pre = _pre;
  s64 sig = _sig;
  if (pre >= sig) {
    res += "0.";
    res.append(uwl(pre - sig), '0');
    res += _int.abs().str();
  }
  else if (pre < 0) {
    res += _int.abs().str();
    res.append(uwl(-pre), '0');
  }
  else {
    res += _int.abs().str();
    if (pre > 0 && pre <= res.length())
      res .insert(res.length() - pre, 1, '.');
  }
  return res;
}


s32 Decimal::cmp (const Decimal& other) const noexcept {
  auto sig1 = _int.sign();
  auto sig2 = other._int.sign();
  if (sig1 != sig2)
    return sig1 < sig2 ? -1 : 1;
  if (!sig1)
    return 0;
  s32 max1 = _sig - _pre;
  s32 max2 = other._sig - other._pre;
  if (max1 != max2)
    return max1 < max2 ? -sig1 : sig1;
  Integer int1 = _int;
  Integer int2 = other._int;
  s32 dpre = _pre - other._pre;
  if (dpre < 0)
    int1 = int1 * __pow10(u32(-dpre));
  else
    int2 = int2 * __pow10(u32(dpre));
  return int1.cmp(int2);
}


Decimal Decimal::round (s32 precision, RoundMode mode) const noexcept {
  s32 dpre = precision - _pre;
  if (dpre == 0)
    return *this;
  if (dpre > 0) {
    Integer i = _int * __pow10(u32(dpre));
    return {i, precision, _sig + dpre};
  }
  Integer pow10 = __pow10(u32(-dpre));
  IntegerQR qr = Integer::div_mod(_int, pow10);
  Integer i = __round(pow10, qr.q, qr.r, mode, _int.sign());
  return {i, precision, _sig + dpre};
}


Decimal Decimal::div (const Decimal& op1, const Decimal& op2, s32 precision, RoundMode mode) {
  Integer int1 = op1._int;
  Integer int2 = op2._int;
  s32 rpre = precision + op2._pre;
  s32 dpre = rpre - op1._pre;
  if (dpre > 0)
    int1 = int1 * __pow10(u32(dpre));
  else if (dpre < 0)
    int2 = int2 * __pow10(u32(-dpre));
  IntegerQR qr = Integer::div_mod(int1, int2);
  Integer q = __round(int2, qr.q, qr.r, mode, int1.sign() * int2.sign());
  return {q, precision, __digit_count(q)};
}


Decimal Decimal::operator+ (const Decimal& other) const noexcept {
  Integer int1 = _int;
  Integer int2 = other._int;
  s32 rpre = _pre;
  s32 dpre = _pre - other._pre;
  if (dpre) {
    if (dpre < 0) {
      rpre = other._pre;
      int1 = int1 * __pow10(u32(-dpre));
    }
    else
      int2 = int2 * __pow10(u32(dpre));
  }
  Integer intr = int1 + int2;
  return {intr, rpre, __digit_count(intr)};
}

Decimal Decimal::operator- (const Decimal& other) const noexcept {
  Integer int1 = _int;
  Integer int2 = other._int;
  s32 rpre = _pre;
  s32 dpre = _pre - other._pre;
  if (dpre) {
    if (dpre < 0) {
      rpre = other._pre;
      int1 = int1 * __pow10(u32(-dpre));
    }
    else
      int2 = int2 * __pow10(u32(dpre));
  }
  Integer intr = int1 - int2;
  return {intr, rpre, __digit_count(intr)};
}

Decimal Decimal::operator* (const Decimal& other) const noexcept {
  Integer intr = _int * other._int;
  return {intr, _pre + other._pre, __digit_count(intr)};
}

Decimal Decimal::operator/ (const Decimal& other) const noexcept {
  s32 rpre = _pre > other._pre ? _pre : other._pre;
  return div(*this, other, rpre, RoundMode::HalfUp);
}


bool Decimal::operator== (const Decimal& other) const noexcept {
  return true;
}

bool Decimal::operator!= (const Decimal& other) const noexcept {
  return true;
}

bool Decimal::operator< (const Decimal& other) const noexcept {
  return true;
}

bool Decimal::operator<= (const Decimal& other) const noexcept {
  return true;
}

bool Decimal::operator> (const Decimal& other) const noexcept {
  return true;
}

bool Decimal::operator>= (const Decimal& other) const noexcept {
  return true;
}


CLOSE_JLIB_NS






#include <jni.h>

USE_JLIB_NS

#define JNI_METHOD(return_value, name) JNIEXPORT return_value JNICALL Java_CDecimal_ ## name


inline static Decimal* fromJavaString (JNIEnv* jenv, jstring jsrc) {
  auto len = (u32)jenv->GetStringUTFLength(jsrc);
  auto src = jenv->GetStringUTFChars(jsrc, nullptr);
  auto dst = new Decimal(src, len);
  jenv->ReleaseStringUTFChars(jsrc, src);
  return dst;
}


extern "C" {

JNI_METHOD(jlong, nnew) (JNIEnv* jenv, jclass jcls, jstring jsrc) {
  return (jlong)fromJavaString(jenv, jsrc);
}

JNI_METHOD(void, ndelete) (JNIEnv* jenv, jclass jcls, jlong jref) {
  delete (Decimal*)jref;
}

JNI_METHOD(jint, ncmp) (JNIEnv* jenv, jclass jcls, jlong jop1, jlong jop2) {
  auto op1 = (Decimal*)jop1;
  auto op2 = (Decimal*)jop2;
  auto res = op1->cmp(*op2);
  return (jint)res;
}

JNI_METHOD(jlong, nadd) (JNIEnv* jenv, jclass jcls, jlong jop1, jlong jop2) {
  auto op1 = (Decimal*)jop1;
  auto op2 = (Decimal*)jop2;
  auto res = op1->operator+(*op2);
  return (jlong)new Decimal(res);
}

JNI_METHOD(jlong, nsub) (JNIEnv* jenv, jclass jcls, jlong jop1, jlong jop2) {
  auto op1 = (Decimal*)jop1;
  auto op2 = (Decimal*)jop2;
  auto res = op1->operator-(*op2);
  return (jlong)new Decimal(res);
}

JNI_METHOD(jlong, nmul) (JNIEnv* jenv, jclass jcls, jlong jop1, jlong jop2) {
  auto op1 = (Decimal*)jop1;
  auto op2 = (Decimal*)jop2;
  auto res = op1->operator*(*op2);
  return (jlong)new Decimal(res);
}

JNI_METHOD(jlong, ndiv) (JNIEnv* jenv, jclass jcls, jlong jop1, jlong jop2, jint jmode) {
  auto op1 = (Decimal*)jop1;
  auto op2 = (Decimal*)jop2;
  auto pre1 = op1->precision();
  auto pre2 = op2->precision();
  auto prer = pre1 > pre2 ? pre1 : pre2;
  auto mode = (Decimal::RoundMode)jmode;
  auto res = Decimal::div(*op1, *op2, prer, mode);
  return (jlong)new Decimal(res);
}

JNI_METHOD(jlong, nneg) (JNIEnv* jenv, jclass jcls, jlong jref) {
  auto ref = (Decimal*)jref;
  auto res = ref->operator-();
  return (jlong)new Decimal(res);
}

JNI_METHOD(jlong, nabs) (JNIEnv* jenv, jclass jcls, jlong jref) {
  auto ref = (Decimal*)jref;
  auto res = ref->abs();
  return (jlong)new Decimal(res);
}

JNI_METHOD(jlong, npow) (JNIEnv* jenv, jclass jcls, jlong jref, jint jexp) {
  auto ref = (Decimal*)jref;
  auto exp = (u32)jexp;
  auto res = ref->pow(exp);
  return (jlong)new Decimal(res);
}

JNI_METHOD(jobject, nmeta) (JNIEnv* jenv, jclass jcls, jlong jref) {
  auto ref = (Decimal*)jref;
  auto cls = jenv->FindClass("Decimal$Meta");
  auto method = jenv->GetMethodID(cls, "<init>", "()V");
  auto obj = jenv->NewObject(cls, method);
  auto field1 = jenv->GetFieldID(cls, "sign", "I");
  jenv->SetIntField(obj, field1, (jint)ref->sign());
  auto field2 = jenv->GetFieldID(cls, "precision", "I");
  jenv->SetIntField(obj, field2, (jint)ref->precision());
  auto field3 = jenv->GetFieldID(cls, "significant", "I");
  jenv->SetIntField(obj, field3, (jint)ref->significant());
  auto field4 = jenv->GetFieldID(cls, "value", "Ljava/lang/String;");
  auto str = ref->str();
  jenv->SetObjectField(obj, field4, jenv->NewStringUTF(str.c_str()));
  return obj;
}

JNI_METHOD(jlong, ncount) (JNIEnv* jenv, jclass jcls) {
  return (jlong)Decimal::__counter;
}

}
